// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../../lib/codemirror"), require("../../../mode/sql/sql"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../../lib/codemirror", "../../../mode/sql/sql"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
  "use strict";

  var tables;
  var sqlTables;
  var tempTables;
  var allTables;
  var alisaRuleStr="[a-z]+[0-9]*";
  var codeRuleStr="((\"[\u4E00-\u9FA50-9a-z _]+\")|(\\[[\u4E00-\u9FA50-9a-z _]+\\])|(`[\u4E00-\u9FA50-9a-z _]+`)|([a-z]+[0-9]*(_[a-z]+[0-9]*)*))";
  var codeRuleStr2="((\"[\u4E00-\u9FA50-9a-z _]+\")|(\\[[\u4E00-\u9FA50-9a-z _]+\\])|(`[\u4E00-\u9FA50-9a-z _]+`)|([a-z]+[0-9]*(_[a-z]+[0-9]*)*))";
  var codeRuleStr3="^(\"|`|\\[)";
  var alisaTempKey="ALISAALISAALISA";
  var defaultTable;
  var keywords;
  var identifierQuote;
  var CONS = {
    QUERY_DIV: ";",
    ALIAS_KEYWORD: "AS"
  };
  var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;
  
  function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }

  function getKeywords(editor) {
    var mode = editor.options.sqlMode || editor.doc.modeOption;
    if (mode === "sql") mode = "text/x-sql";
    return CodeMirror.resolveMode(mode).keywords;
  }

  function getIdentifierQuote(editor) {
    var mode = editor.options.sqlMode || editor.doc.modeOption;
    if (mode === "sql") mode = "text/x-sql";
    return CodeMirror.resolveMode(mode).identifierQuote || "`";
  }

  function getText(item) {
    return typeof item == "string" ? item : item.text;
  }

  function wrapTable(name, value) {
    if (isArray(value)) value = {columns: value}
    if (!value.text) value.text = name
    return value
  }

  function parseTables(input) {
    var result = {}
    if (isArray(input)) {
      for (var i = input.length - 1; i >= 0; i--) {
        var item = input[i]
        result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
      }
    } else if (input) {
      for (var name in input)
        result[name.toUpperCase()] = wrapTable(name, input[name])
    }
    return result
  }

  function getTable(name) {
    return tables[name.toUpperCase()]
  }

  function shallowClone(object) {
    var result = {};
    for (var key in object) if (object.hasOwnProperty(key))
      result[key] = object[key];
    return result;
  }

  function match(string, word) {
    var len = string.length;
    var sub = getText(word).substr(0, len);
    return string.toUpperCase() === sub.toUpperCase();
  }

  function addMatches(result, search, wordlist, formatter) {
    if (isArray(wordlist)) {
      for (var i = 0; i < wordlist.length; i++)
        if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
    } else {
      for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
        var val = wordlist[word]
        if (!val || val === true)
          val = word
        else
          val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
        if (match(search, val)) result.push(formatter(val))
      }
    }
  }

  function cleanName(name) {
    // Get rid name from identifierQuote and preceding dot(.)
    if (name.charAt(0) == ".") {
      name = name.substr(1);
    }
    // replace duplicated identifierQuotes with single identifierQuotes
    // and remove single identifierQuotes
    var nameParts = name.split(identifierQuote+identifierQuote);
    for (var i = 0; i < nameParts.length; i++)
      nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,"g"), "");
    return nameParts.join(identifierQuote);
  }

  function insertIdentifierQuotes(name) {
    var nameParts = getText(name).split(".");
    for (var i = 0; i < nameParts.length; i++)
      nameParts[i] = identifierQuote +
        // duplicate identifierQuotes
        nameParts[i].replace(new RegExp(identifierQuote,"g"), identifierQuote+identifierQuote) +
        identifierQuote;
    var escaped = nameParts.join(".");
    if (typeof name == "string") return escaped;
    name = shallowClone(name);
    name.text = escaped;
    return name;
  }

  function nameCompletion(cur, token, result, editor) {
    // Try to complete table, column names and return start position of completion
    var useIdentifierQuotes = false;
    var nameParts = [];
    var start = token.start;
    var cont = true;
    while (cont) {
      cont = (token.string.charAt(0) == ".");
      useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote);

      start = token.start;
      nameParts.unshift(cleanName(token.string));

      token = editor.getTokenAt(Pos(cur.line, token.start));
      if (token.string == ".") {
        cont = true;
        token = editor.getTokenAt(Pos(cur.line, token.start));
      }
    }

    // Try to complete table names
    var string = nameParts.join(".");
    addMatches(result, string, tables, function(w) {
      return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
    });

    // Try to complete columns from defaultTable
    addMatches(result, string, defaultTable, function(w) {
      return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
    });

    // Try to complete columns
    string = nameParts.pop();
    var table = nameParts.join(".");

    var alias = false;
    var aliasTable = table;
    // Check if table is available. If not, find table by Alias
    if (!getTable(table)) {
      var oldTable = table;
      table = findTableByAlias(table, editor);
      if (table !== oldTable) alias = true;
    }

    var columns = getTable(table);
    if (columns && columns.columns)
      columns = columns.columns;

    if (columns) {
      addMatches(result, string, columns, function(w) {
        var tableInsert = table;
        if (alias == true) tableInsert = aliasTable;
        if (typeof w == "string") {
          w = tableInsert + "." + w;
        } else {
          w = shallowClone(w);
          w.text = tableInsert + "." + w.text;
        }
        return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
      });
    }

    return start;
  }

  function eachWord(lineText, f) {
    var words = lineText.split(/\s+/)
    for (var i = 0; i < words.length; i++)
      if (words[i]) f(words[i].replace(/[`,;]/g, ''))
  }

  function findTableByAlias(alias, editor) {
    var doc = editor.doc;
    var fullQuery = doc.getValue();
    var aliasUpperCase = alias.toUpperCase();
    var previousWord = "";
    var table = "";
    var separator = [];
    var validRange = {
      start: Pos(0, 0),
      end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)
    };

    //add separator
    var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);
    while(indexOfSeparator != -1) {
      separator.push(doc.posFromIndex(indexOfSeparator));
      indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1);
    }
    separator.unshift(Pos(0, 0));
    separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));

    //find valid range
    var prevItem = null;
    var current = editor.getCursor()
    for (var i = 0; i < separator.length; i++) {
      if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
        validRange = {start: prevItem, end: separator[i]};
        break;
      }
      prevItem = separator[i];
    }

    if (validRange.start) {
      var query = doc.getRange(validRange.start, validRange.end, false);

      for (var i = 0; i < query.length; i++) {
        var lineText = query[i];
        eachWord(lineText, function(word) {
          var wordUpperCase = word.toUpperCase();
          if (wordUpperCase === aliasUpperCase && getTable(previousWord))
            table = previousWord;
          if (wordUpperCase !== CONS.ALIAS_KEYWORD)
            previousWord = word;
        });
        if (table) break;
      }
    }
    return table;
  }

  function arrayContains(arr,val){
      if(!arr){
          return false;
      }
      for (var i = 0; i < arr.length; i++) {
         if(arr[i].toLowerCase() === val.toLowerCase()){
            return true;
         }
      }
      return false;
  }
  function getArrayValue(arr,val){
      if(!arr){
          return null;
      }
      for (var i = 0; i < arr.length; i++) {
         if(arr[i].toLowerCase() === val.toLowerCase()){
            return arr[i];
         }
      }
      return null;
  }
  function objectContains(obj,key){
      if(!obj){
          return false;
      }
      for (var k in obj){
          if (k.toLowerCase() === key.toLowerCase()) {
              return true;
          }
      }
      return false;
  }
  
  function hintRender(element, self, data) {
      let div = document.createElement("div");
      div.setAttribute("class", "autocomplete-div");
      if(data.displayTitle){
          div.setAttribute("title", data.displayTitle);
      }
      let divText = document.createElement("div");
      divText.setAttribute("class", "autocomplete-name");
      divText.innerText = data.displayText;
      div.appendChild(divText);
      
      if(data.displayInfo && data.displayInfo !== data.displayText){
          let divInfo = document.createElement("div");
          divInfo.setAttribute("class", "autocomplete-hint");
          divInfo.innerText = data.displayInfo;
          
          let divInfoHidden = document.createElement("div");
          divInfoHidden.setAttribute("class", "autocomplete-hidden");
          divInfoHidden.innerText = "&nbsp;"+data.displayInfo;
          divText.appendChild(divInfoHidden);
          div.appendChild(divInfo);
      }
      element.appendChild(div);
  }
 function displayHandler(text,displayInfo,displayTitle){
      return {
          text:text,
          displayText: text,
          displayInfo: displayInfo,
          displayTitle: displayTitle,
          render: hintRender
      };
 }
  function dynamicAlisaColumns(tableAliasMap,tables,sqlTables,hasAlisaListMap,alisaTables){
      
      var columnsHandler = function(a1,a2,tables,alisa,key){
          if(a2){
              for(var i=0,ln=a1.length; i<ln; i++){
                  tables[alisa].push(displayHandler(a1[i],a2[i],key));
              }
          }else{
              tables[alisa] = a1;
          }
     };
     var columnsHandler2 = function(a1,a2,alisaTables,key){
         for(var i=0,ln=a1.length; i<ln; i++){
             if(a2){
                 alisaTables.push(displayHandler(a1[i],a2[i],key));
             }else{
                 alisaTables.push(a1);
             }
         }
    };
      for(var key in tableAliasMap){
          var t = sqlTables[key];
          if(t && t.length>0){
              var arrs = tableAliasMap[key];
              if(!arrs && arrs.length>0){
                  continue;
              }
              var t0 = t[0];
              var t1 = t.length>1 && t[1];
              var t2 = t.length>2 && t[2];
              for(var i=0,ln=arrs.length;i<ln;i++){
                  var alisa = arrs[i];
                  if(i===0){
                      columnsHandler(t0,t2,tables,key,key);
                      columnsHandler2(t0,t2,alisaTables,key);
                      columnsHandler2(t1,t2,alisaTables,key);
                  }
                  if(alisa !==""){
                      tables[alisa] = [];
                      columnsHandler(t0,t2,tables,alisa,key);
                  }
              }
          }
      }
  }
  
  function analysisAlisaCompletion(tempTables,tables,currTableCodes,inputAlias,text,currKey) {
      if(!text){
          return;
      }
      inputAlias = inputAlias.toLowerCase();
      var groupdata = getNestingDepth(currTableCodes,text, currKey);
      if(!groupdata){
          return;
      }
      var alisalist = [];
      var currlevel = null;
      var currdata = groupdata;
      
      var currAlisaHandler = function(data,alisalist){
          for (var i = 0; i < data.length; i++) {
              var t = data[i];
              var alisa = t.alisa;
              if(alisa){
                  if(!arrayContains(alisalist,alisa)){
                      alisalist.push(alisa.toLowerCase());
                  }
              }
              if(t.hasOwnProperty("currlevel") && t.currlevel===0){
                  currlevel = t.currlevel;
              }
              if(t.children){
                  if(t.hasOwnProperty("currlevel") && t.currlevel===1){
                      currdata = t.children;
                  }
                  currAlisaHandler(t.children,alisalist);
              }
          }
      };
      currAlisaHandler(groupdata,alisalist);
      
     if(!currdata){
         return;
     }
     
     var defaultFieldsHandler = function(t, inputAlias){
         var factcode = getArrayValue(currTableCodes,t.code);
         if(factcode){
             tables[inputAlias] = [];
             var t = tempTables[factcode];
             if(t && t.length>0){
                 for(var i=0,ln=t[0].length; i<ln; i++){
                     tables[inputAlias].push(displayHandler(t[0][i],t[2]?t[2][i]:"",factcode));
                 }
             }
         }
     };
     
    var getFieldsHandler = function(t, inputAlias,firstFlag,ckey){
        if(t.children && t.children.length>0){
            if(firstFlag){
                tables[inputAlias] = [];
            }
            var children = t.children;
            var cmap = {};
            for (var ci = 0; ci < children.length; ci++) {
                var c = children[ci];
                if(firstFlag && c.code){
                    defaultFieldsHandler(c,c.alisa);
                }
                if(c.alisa){
                    cmap[c.alisa.toLowerCase()] = c;
                }
            }
            for (var ci = 0; ci < children.length; ci++) {
                var c = children[ci];
                if(!(alisalist.length>0 && c.fields)){
                    continue;
                }
                   var fields = c.fields.match(new RegExp("(("+alisalist.join("|")+")\\."+codeRuleStr+" (AS| +) "+codeRuleStr2+" *(,|$))"+
                               "|(("+alisalist.join("|")+")\\."+codeRuleStr+"[^,]+ (AS| ) "+codeRuleStr2+" *(,|$))"+
                               "|([^,]*\\(.*?\\).*?(AS| +) "+codeRuleStr2+" *(,|$))"+
                               "|(("+alisalist.join("|")+")\\.\\* *(,|$))|(("+alisalist.join("|")+")\\."+codeRuleStr+" *(,|$))","ig"));
                   if(fields && fields.length>0){
                       for (var j = 0; j < fields.length; j++) {
                          var fstr = fields[j];
                          if(fstr){
                              fstr = fstr.trim().replace(/ *,$/g,"");
                              var fm=fstr.match(new RegExp("^("+alisalist.join("|")+")\\.\\*$","i"));
                              var endFlag=false;
                              if(fm){
                                 //fm[1]
                                 endFlag=true;
                                 console.log("fm1---"+JSON.stringify(fm));
                                 var cdata = cmap[fm[1].toLowerCase()];
                                 if(cdata && cdata.code){
                                     var factcode = getArrayValue(currTableCodes,cdata.code);
                                     if(factcode){
                                         var t = tempTables[factcode];
                                         if(t && t.length>0){
                                             for(var i=0,ln=t[0].length; i<ln; i++){
                                                 if(!firstFlag && ckey !=="*"){
                                                     continue;
                                                 }
                                                 tables[inputAlias].push(displayHandler(t[0][i],t[2]?t[2][i]:"",factcode));
                                             }
                                         }
                                     }
                                 }else{
                                     getFieldsHandler(cdata, inputAlias,false,"*");
                                 }
                              }
                              if(!endFlag){
                                  fm=fstr.match(new RegExp("^("+alisalist.join("|")+")\\.("+codeRuleStr+")$","i"));
                              }
                              if(!endFlag&&fm){
                                  //fm[1] fm[2]
                                  endFlag=true;
                                  var cdata = cmap[fm[1].toLowerCase()];
                                  if(cdata && cdata.code){
                                      var factcode = getArrayValue(currTableCodes,cdata.code);
                                      if(factcode){
                                          var t = tempTables[factcode];
                                          if(t && t.length>0){
                                              for(var i=0,ln=t[0].length; i<ln; i++){
                                                  if(!firstFlag && ckey.toLowerCase() !== fm[2].toLowerCase()){
                                                      continue;
                                                   }
                                                  if(fm[2].toLowerCase()===t[0][i].toLowerCase()){
                                                      tables[inputAlias].push(displayHandler(t[0][i],t[2]?t[2][i]:"",factcode));
                                                  }
                                              }
                                          }
                                      }
                                  }else{
                                       getFieldsHandler(cdata, inputAlias,false,fm[2]);
                                  }
                              }
                              if(!endFlag){
                                  fm=fstr.match(new RegExp("^("+alisalist.join("|")+")\\.("+codeRuleStr+") AS ("+codeRuleStr2+")$","i"));
                                  if(!fm){
                                      fm=fstr.match(new RegExp("^("+alisalist.join("|")+")\\.("+codeRuleStr+") AS ("+codeRuleStr+")$","i")); 
                                  }
                              }
                              if(!endFlag&&fm){
                                  // fm[1] fm[2] fm[4]/fm[9]
                                  endFlag=true;
                                  var cdata = cmap[fm[1].toLowerCase()];
                                  if(cdata && cdata.code){
                                      var factcode = getArrayValue(currTableCodes,cdata.code);
                                      if(factcode){
                                          var t = tempTables[factcode];
                                          if(t && t.length>0){
                                              for(var i=0,ln=t[0].length; i<ln; i++){
                                                  if(!firstFlag && ckey.toLowerCase() !== fm[9].toLowerCase()){
                                                       continue;
                                                  }
                                                  if(fm[2].match(new RegExp(codeRuleStr3))){
                                                      fm[2]=fm[2].substring(1,fm[2].length-1);
                                                  }
                                                  if(fm[2].toLowerCase()===t[0][i].toLowerCase()){
                                                      tables[inputAlias].push(displayHandler(fm[9],t[2]?t[2][i]:"",factcode));
                                                  }
                                              }
                                          }
                                      }
                                  }else{
                                       getFieldsHandler(cdata, inputAlias,false,fm[9]);
                                  }
                              }
                              if(!endFlag){
                                  fm=fstr.match(new RegExp("\\b("+alisalist.join("|")+")\\.("+codeRuleStr+")\\b|(?<= AS *)"+codeRuleStr+"$","ig"));
                              }
                              if(!endFlag&&fm){
                                   endFlag=true;
                                   var keys = [];
                                   var descs = [];
                                   for (var mj = 0,fln=fm.length-1; mj < fln; mj++) {
                                       var tarr = fm[mj].split(".");
                                       var cdata = cmap[tarr[0].toLowerCase()];
                                       if(cdata && cdata.code){
                                           var factcode = getArrayValue(currTableCodes,cdata.code);
                                           if(factcode){
                                               var t = tempTables[factcode];
                                               if(t && t.length>0){
                                                   for(var i=0,ln=t[0].length; i<ln; i++){
                                                       if(tarr[1].toLowerCase()===t[0][i].toLowerCase()){
                                                           if(!arrayContains(keys,factcode)){
                                                                keys.push(factcode);
                                                           }
                                                           if(t[2] && !arrayContains(descs,t[2][i]) && descs.length<5){
                                                              descs.push(t[2][i]);
                                                           }
                                                       }
                                                   }
                                               }
                                           }
                                       }
                                   }
                                   if(keys.length>0){
                                        tables[inputAlias].push(displayHandler(fm[fm.length-1],descs.join("+"),keys.join("+")));
                                   }else{
                                        tables[inputAlias].push(displayHandler(fm[fm.length-1],null,factcode));
                                   }
                              }
                          }
                       }
                   }
            }
            
        }
    };
    
    var curralisamap = {};
    for (var i = 0; i < currdata.length; i++) {
        var t = currdata[i];
        var alisa = t.alisa;
        if(alisa){
            curralisamap[alisa.toLowerCase()] = t;
        }
    }
    var t = curralisamap[inputAlias];
    if(t){
        if(t.code){
            defaultFieldsHandler(t,inputAlias);
        }
        getFieldsHandler(t, inputAlias,true,"");
    }
  }
  function getNestingDepth(currTableCodes,sql,currKey) {
      var maxlevel=0;  
      var level=0;
      var currlevel = null;
      var groups =[];
      var str = "";
      var lastend = "";
      var num = 0;
      for (var i = 0; i < sql.length; i++) {
          var c = sql[i];
          str=str + c;
          switch(c) {
              case '(':
                 
                level++;
                break;
              case ')':
                if (maxlevel<level) {
                    maxlevel = level;
                }
              
                level--;
                break;
              default:
              if(i === sql.indexOf(currKey)){
                 currlevel = level;
              }
          }
         var keym = str && str.match(new RegExp("( (JOIN|WHERE) )$","i"));
         if((level ===0 && keym) || i===sql.length-1){
           str = str.trim();
           if(lastend && lastend.toLowerCase() ==="where"){
               str = "where "+str;
           }
           lastend = keym && keym[0].trim();
           var t ={
              sqlstr: str,
              idx: num,
              maxlevel: maxlevel,
           };
           groups.push(t);
           num++;
           if(currlevel!=null){
               t.currlevel= currlevel;
           }
           var subm2=str.match(new RegExp("\\bFROM *\\( *SELECT\\b.*\\bFROM\\b.*\\) *("+alisaRuleStr+")","i"));
           if(str.substr(0,1)==="(" || subm2){
             if(currlevel!=null){
                   var idx = str.indexOf(currKey);
                   var idx2 = idx+currKey.length;
                   t.currlevel = t.currlevel-getNestingDepth2(currTableCodes,str,currKey,idx,idx2);
             }
             var m1 = str.match(new RegExp("\\((.*\\b("+currTableCodes.join("|")+")\\b.*)\\) *("+alisaRuleStr+")","i"));
             if(m1){
               t.alisa = m1[3];
               var cstr = m1[1];
               var children = getNestingDepth(currTableCodes,cstr,currKey);
               t.children = children;
             }else{
               t.iserror = true;
             }
           }else if(str.substr(0,5).toLowerCase() ==="where"){
                t.iswhere = true;
           }else{
             var m2 = str.match(new RegExp("\\b("+currTableCodes.join("|")+") ("+alisaRuleStr+")\\b","i"));
             if(m2){
               t.alisa = m2[2];
               t.code = m2[1];
               
               m2 = str.match(new RegExp("(?<=^SELECT\\b +).*?(?= +\\bFROM\\b)","i"));
               if(m2){
                 t.fields = m2[0];
               }
             }else{
               t.iserror = true;
             }
           }
           maxlevel=0;
           currlevel = null;
           str="";
           
         }
         
      }
      
      return  groups;
  }
  
  function getNestingDepth2(currTableCodes,sql,currKey,idx,idx2){
      var maxlevel=0;  
      var level=0;
      var currlevel = null;
      
      var num = 0;
      var str = "";
      var i=0;
      var func = function(str,idx){
         if (str.substr(0,1) != "(" && idx-i>=0)  {
           i++;
           str = sql.substr(idx-i,i);
           return func(str,idx);
         }else{
           return str;
         }
      };
     str = func(str,idx);
     var j= 0;
     var str2 = "";
     var func2 = function(str2,idx2){
         if (str2.substr(str2.length-1,1) != ")" && idx2+j<sql.length)  {
           j++;
           str2 = sql.substr(idx2,j);
           return func2(str2,idx2);
         }else{
           return str2;
         }
      };
      str = str + func2(str2,idx2);
      
      if(!str.match(new RegExp("\\b("+currTableCodes.join("|")+")\\b","i"))){
         num++;
         num = num + getNestingDepth2(currTableCodes,sql,currKey,idx-i,idx2+j);
      }else{
      }
     return num;
   }
  
  CodeMirror.registerHelper("hint", "mybatissql", function(editor, options) {
    var cur = editor.getCursor();
    var result = [];
    var token = editor.getTokenAt(cur), start, end, search;
    if (token.end > cur.ch) {
      token.end = cur.ch;
      token.string = token.string.slice(0, cur.ch - token.start);
    }
    if (token.string.match(/^[.`"'\w@][\w$#]*$/g)) {
        search = token.string;
        start = token.start;
        end = token.end;
    } else {
        start = end = cur.ch;
        search = "";
    }
    
    keywords = getKeywords(editor);
    var tablesList = [];
    try{
        sqlTables = options && options.sqlTables;
        allTables =  options && options.allTables;
        if(allTables){
             for(key in allTables){
                 if(!objectContains(keywords,key)){
                     tablesList.push(key);
                 }else{
                     delete allTables[key];
                 }
             }
        }
        var addTableList = function(tables,tablesList){
            for(var ki=0;ki<tablesList.length;ki++){
                var key = tablesList[ki];
                if(!objectContains(tables,key)){
                    tables[key] = [];
                }
            }
        };
        
        if(options && options.sqlTablesHint && typeof options.sqlTablesHint === "function"){
            try{
                var sqlTables2 = options.sqlTablesHint(editor,options,tempTables);
                sqlTables = {};
                if(sqlTables2){
                    for (var key in sqlTables2){
                        if(!objectContains(sqlTables,key) && !objectContains(keywords,key)){
                            sqlTables[key] = sqlTables2[key];
                        }
                    }
                }
            }catch(b){
                console.log(b);
            }
        }
        if(sqlTables && typeof sqlTables === "object"){
            for (var key in sqlTables){
                if(objectContains(keywords,key)){
                    delete sqlTables[key];
                }
            }
            
            options.tables = {};
            tables = options.tables;
            if(tablesList.length===0){
                for (var key in sqlTables){
                    if(!arrayContains(tablesList,key)){
                        tablesList.push(key);
                    }
                }
            }
            if(tablesList && tablesList.length>0){
                addTableList(tables,tablesList);
                var curSqlTag = null;
                var closeMybatis = editor.options && editor.options.closeMybatis;
                if(!closeMybatis){
                    var context = token.state.context;
                    var isSqlUpdateTag = false;
                    if(context){
                        var f = context.isSqlTag;
                        while(!f && context.prev){
                            f = context.prev.isSqlTag;
                            context = context.prev;
                        }
                        if(f){
                          curSqlTag = context.tagName;
                        }
                        isSqlUpdateTag=context.isSqlUpdateTag
                    }
                }
                if(curSqlTag || closeMybatis){
                    var allLineStr = editor.getValue(" ");
                    var tableCodesRule =new RegExp("\\b("+tablesList.join("|")+")\\b","ig");
                    if(!tempTables){
                        tempTables = {};
                        allLineStr = editor.getMode().clearContentComment(allLineStr,true);
                        var allCodes = allLineStr.match(tableCodesRule);
                        if(allCodes && allCodes.length>0){
                            for(var ki=0;ki<allCodes.length;ki++){
                                var key = allCodes[ki];
                                if(!objectContains(keywords,key) && !objectContains(tempTables,key)){
                                    var factkey = getArrayValue(tablesList,key);
                                    if(factkey){
                                        tempTables[factkey] = [];
                                    }
                                }
                            }
                        }
                    }else{
                        for (var key in tempTables){
                            if(tempTables[key].length===0){
                                delete tempTables[key];
                            }
                        }
                    }
                    
                    var usedTableCodes = [];
                    var inputAlias;
                    var useIdentifierQuotes = false;
                    var nameParts = [];
                    var startx = token.start;
                    var cont = true;
                    var tokenx = token;
                    while (cont) {
                      cont = (tokenx.string.charAt(0) == ".");
                      useIdentifierQuotes = useIdentifierQuotes || (tokenx.string.charAt(0) == identifierQuote);

                      startx = tokenx.start;
                      nameParts.unshift(cleanName(tokenx.string));

                      tokenx = editor.getTokenAt(Pos(cur.line, tokenx.start));
                      if (tokenx.string == ".") {
                        cont = true;
                        tokenx = editor.getTokenAt(Pos(cur.line, tokenx.start));
                      }
                    }
                    inputAlias = nameParts[0];
                    if(!inputAlias.match(new RegExp("^"+alisaRuleStr+"$","i"))){
                        inputAlias = null;
                    }
                    
                    var startLine = cur.line;
                    var startLineStr = editor.getLine(startLine);
                    if(inputAlias){
                        startLineStr = startLineStr.substr(0,cur.ch-1-(inputAlias.length)) + " "+alisaTempKey+" " + startLineStr.substr(cur.ch,startLineStr.length);
                    }
                    while((closeMybatis&&startLine>0)||(editor.getLine(startLine).indexOf("<"+curSqlTag) ===-1 && startLine>1)){
                        startLine--;
                        startLineStr = editor.getLine(startLine) +" "+ startLineStr;
                    }
                    var endLine = cur.line;
                    while((closeMybatis || editor.getLine(endLine).indexOf("</"+curSqlTag+">") === -1) &&  endLine<editor.lineCount()-1){
                        endLine++;
                        startLineStr = startLineStr +" "+ editor.getLine(endLine);
                    }
                    
                    startLineStr = editor.getMode().getRefTagContent(startLineStr, allLineStr);
                    var currTableCodes = startLineStr.match(tableCodesRule);
                    if(currTableCodes && currTableCodes.length>0){
                        for(var ki=0;ki<currTableCodes.length;ki++){
                            var key = currTableCodes[ki];
                            var factkey = getArrayValue(tablesList,key);
                            if(!objectContains(keywords,key) && !objectContains(tempTables,key)){
                                if(factkey){
                                    tempTables[factkey] = [];
                                }
                            }
                            if(!arrayContains(usedTableCodes,key)){
                                if(factkey){
                                    usedTableCodes.push(factkey);
                                }
                            }
                        }
                    }
                    
                    for (var key in sqlTables){
                        var t = sqlTables[key];
                        tempTables[key] = (t.length>0 && t[0].length>0)?t:[];
                    }
                    
                    if(usedTableCodes.length>0){
                        var singleTableFlag = usedTableCodes.length===1;
                        var subselect=startLineStr.match(new RegExp("FROM *\\(","i"));
                        if(subselect){
                            singleTableFlag=false;
                        }
                        var tableWithAliasRegs =new RegExp("\\b("+usedTableCodes.join("|")+") +("+alisaRuleStr+")*\\b","ig");
                        var tableWithAlias = startLineStr.match(tableWithAliasRegs);
                        
                        var handlerTableAlias = function(tableAliasMap,keywords,str){
                            var t = str.split(" ");//table t1
                            var factkey = getArrayValue(tablesList,t[0]);
                            if(factkey){
                                if(!tableAliasMap.hasOwnProperty(factkey)){
                                     tableAliasMap[factkey] = [""];
                                }
                                var code = !objectContains(keywords,t[1])?t[1]:"";
                                if(!arrayContains(tableAliasMap[factkey],code)){
                                    tableAliasMap[factkey].push(code);
                                }
                            }
                        };
                        
                        var tableAliasMap = {};
                        var hasAlisaListMap = {};
                        var alisaTables=[];
                        if(tableWithAlias && tableWithAlias.length>0){
                            if(singleTableFlag){
                                handlerTableAlias(tableAliasMap,keywords,tableWithAlias[0]);
                                
                                dynamicAlisaColumns(tableAliasMap,tables,sqlTables,hasAlisaListMap,alisaTables);
                            }else{
                                for(var ki=0;ki<tableWithAlias.length;ki++){
                                    handlerTableAlias(tableAliasMap,keywords,tableWithAlias[ki]);
                                }
                                dynamicAlisaColumns(tableAliasMap,tables,sqlTables,hasAlisaListMap,alisaTables);
                            }
                        }
                        if(!singleTableFlag && inputAlias){
                            try{
                                analysisAlisaCompletion(tempTables,tables,currTableCodes,inputAlias,startLineStr, alisaTempKey);
                            }catch(b1){
                                  console.log(b1);
                            }
                        }
                        if(alisaTables.length>0){
                            tables["currAlisaTables"] = alisaTables;
                            options.defaultTable = "currAlisaTables";
                        }
                    }
                }
            }
        }else if(tablesList && tablesList.length>0){
            options.tables = {};
            tables = options.tables;
            addTableList(tables,tablesList);
        }
    }catch(b){
        console.log(b);
    }
    
    tables = parseTables(options && options.tables)
    var defaultTableName = options && options.defaultTable;
    var disableKeywords = options && options.disableKeywords;
    defaultTable = defaultTableName && getTable(defaultTableName);
   
    identifierQuote = getIdentifierQuote(editor);

    if (defaultTableName && !defaultTable)
      defaultTable = findTableByAlias(defaultTableName, editor);

    defaultTable = defaultTable || [];
    if (defaultTable.columns)
      defaultTable = defaultTable.columns;

    
    if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) {
      start = nameCompletion(cur, token, result, editor);
    } else {
      var objectOrClass = function(w, className) {
        if (typeof w === "object") {
          w.className = className;
        } else {
          w = { text: w, className: className };
        }
        return w;
      };
      addMatches(result, search, defaultTable, function(w) {
          return objectOrClass(w, "CodeMirror-hint-table CodeMirror-hint-default-table");
      });
      addMatches(
          result,
          search,
          tables, function(w) {
            return objectOrClass(w, "CodeMirror-hint-table");
          }
      );
      if (!disableKeywords)
        addMatches(result, search, keywords, function(w) {
            return objectOrClass(w.toUpperCase(), "CodeMirror-hint-keyword");
        });
      
      if(result && result.length>0 && arrayContains(tablesList,result[0].text)){
          for(var i=0,ln=result.length;i<ln; i++){
             result[i].displayText = result[i].text;
             result[i].displayInfo = allTables[result[i].text];
             result[i].render = hintRender;
          }
       }
  }
    
    return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};
  });
});
